package com.wjerp.tplus.server.common.security;

import com.wjerp.tplus.server.common.constant.ErrorDetail;
import com.wjerp.tplus.server.common.constant.RedisKey;
import com.wjerp.tplus.server.common.exception.BusinessException;
import com.wjerp.tplus.server.common.utils.RedisUtils;
import com.wjerp.tplus.server.domain.entity.CompanyInfo;
import com.wjerp.tplus.server.domain.entity.sys.UserInfo;
import com.wjerp.tplus.server.domain.service.UserInfoService;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;


/**
 * @author lishuailei
 */
@Component
public class JwtTokenUtil implements Serializable {

    private static final Long serialVersionUID = -3301605591108950415L;

    static final String CLAIM_KEY_USERNAME = "sub";
    static final String CLAIM_KEY_AUDIENCE = "aud";
    static final String CLAIM_KEY_CREATED = "iat";

    @Value("${jwt.issuer}")
    private String issuer;
    
    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration}")
    private Long expiration;

    /**
     * 临时过期时间
     */
    @Value("${jwt.interim_expiration}")
    private Long interim_expiration;

    @Resource
    private UserInfoService userInfoService;

    @Resource
    private RedisUtils redisUtils;

    public String getUsernameFromToken(String token)throws BusinessException {
        return getClaimFromToken(token, Claims::getSubject);
    }

    public String getUserDisplayIdFromToken(String token) throws BusinessException {
        JwtUser jwtUser = getUserInfoFromToken(token);
        return jwtUser.getDisplayUserId();
    }
    
    public String getIssuerFromToken(String token)throws BusinessException{
    	return getClaimFromToken(token, Claims::getIssuer);
    }

    public Date getIssuedAtDateFromToken(String token)throws BusinessException {
        return getClaimFromToken(token, Claims::getIssuedAt);
    }

    public Date getExpirationDateFromToken(String token)throws BusinessException {
        return getClaimFromToken(token, Claims::getExpiration);
    }

    public String getAudienceFromToken(String token)throws BusinessException {
        return getClaimFromToken(token, Claims::getAudience);
    }

    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver)throws BusinessException {
        final Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);
    }

    private Claims getAllClaimsFromToken(String token) throws BusinessException{
    	Claims result = null;
        try{
        	result = Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();

        }catch(ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException e){
        	throw new BusinessException(ErrorDetail.AUTH_INVALID_TOKEN);
        }
        //doRefreshToken(result);
        return result;
    }

    private Boolean isTokenNotExpired(String displayUserId, String authToken)throws BusinessException {
        if(redisUtils.exists(RedisKey.LOGIN_USER_TOKEN+displayUserId+authToken)){
            redisUtils.set(RedisKey.LOGIN_USER_TOKEN+displayUserId+authToken, authToken, interim_expiration);
            return true;
        }
        return  false;
    }

    public String generateToken(UserInfo platformUserInfo, List<String> roles, CompanyInfo companyInfo) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("companyCode",companyInfo.getCompanyCode());
        claims.put("role",roles.toArray());
        claims.put("enabled", platformUserInfo.getEnabled());
        String displayUserId = platformUserInfo.getDisplayUserId();
        claims.put("id", displayUserId);
        claims.put("userType",platformUserInfo.getUserType());
        String token= doGenerateToken(claims, platformUserInfo.getMobile());
        redisUtils.set(RedisKey.LOGIN_USER_TOKEN+displayUserId+token,token,interim_expiration);
        return token;
    }

    private String doGenerateToken(Map<String, Object> claims, String subject) {
    	final Date createdDate = new Date();
        System.out.println("******start doGenerateToken " + createdDate + "********");
        
        String result = Jwts.builder()
        		.setClaims(claims)
        		.setIssuer(issuer)                
                .setSubject(subject)
                .setIssuedAt(createdDate)
                //.setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
        final Date endDate = new Date();
        System.out.println("*****end doGnerateToken" + endDate + "******");
        System.out.println("time used:" + (endDate.getTime() - createdDate.getTime()) + " ms");
        return result;
    }


    public Boolean validateToken(String authToken)throws BusinessException {
    	String issuer = getIssuerFromToken(authToken);
    	String userDisplayId = getUserDisplayIdFromToken(authToken);
    	if(issuer == null || userDisplayId == null){
    		return false;
    	}else{
    		return (issuer.equals(this.issuer) && isTokenNotExpired(userDisplayId, authToken));
    	}
    }
    
	public JwtUser getUserInfoFromToken(String token)throws BusinessException {
    	UserInfo platformUserInfo = new UserInfo();
    	Claims claims = getAllClaimsFromToken(token);
    	
    	platformUserInfo.setDisplayUserId((String) claims.get("id"));
    	platformUserInfo.setMobile((String)claims.get("sub"));
    	platformUserInfo.setEnabled((Boolean) claims.get("enabled"));

        platformUserInfo.setUserType((Integer)claims.get("userType"));
        String companyCode = claims.get("companyCode").toString();
        platformUserInfo.setUserType((Integer)claims.get("userType"));
        List<String> roles= userInfoService.findUserPermissionByDisplayUserIdAndUserType(platformUserInfo.getDisplayUserId(),platformUserInfo.getUserType());
    	return JwtUserFactory.create(platformUserInfo,roles,companyCode);
    }
}